#include <Kharlia/Engine/Common.h>
#include <Kharlia/Engine/Converters.h>
#include <Kharlia/Engine/Init.h>
#include <Kharlia/Engine/Units.h>
#include <Kharlia/Core/Converters.h>

#include <CEGUIUDim.h>
#include <CEGUIString.h>

#include <Kharlia/Engine/Debug.h>
#include <boost/type_traits/is_pod.hpp>

namespace kharlia { namespace engine {
    // Types in other namespaces must be typedefed for converter macro compatibility

    typedef ui::UDim CEGUI_UDim;

    // UI::UDim - Python Tuple
    struct CEGUI_UDim_To {
        static PyObject* convert(ui::UDim const& v) {
            handle<> out(PyList_New(2));
            handle<> f1(PyFloat_FromDouble(v.d_scale));
            handle<> f2(PyFloat_FromDouble(v.d_offset));
            PyList_SetItem(out.get(), 0, incref(f1.get()));
            PyList_SetItem(out.get(), 1, incref(f2.get()));
            return incref(out.get());
        }
    };
    struct CEGUI_UDim_From {
        static void* convertible(PyObject* obj) {
            if (PySequence_Check(obj) && PySequence_Size(obj) == 2) {
                handle<> i1(PySequence_GetItem(obj, 0));
                handle<> i2(PySequence_GetItem(obj, 1));
                if (PyNumber_Check(i1.get()) && PyNumber_Check(i2.get())) {
                    return obj;
                }
            }
            return nullptr;
        }
        static void construct(PyObject* obj, converter::rvalue_from_python_stage1_data* data) {
            handle<> n1(PySequence_GetItem(obj, 0));
            handle<> n2(PySequence_GetItem(obj, 1));

            handle<> f1(PyNumber_Float(n1.get()));
            handle<> f2(PyNumber_Float(n2.get()));

            KH_GET_STORAGE(CEGUI_UDim);
            new(storage) CEGUI_UDim((float)PyFloat_AS_DOUBLE(f1.get()),
                                    (float)PyFloat_AS_DOUBLE(f2.get()));
            KH_SET_STORAGE;

        }
    };

    struct CEGUI_UVector2_To {
        static PyObject* convert(ui::UVector2 const& v) {
            handle<> out(PyList_New(2));
            PyList_SetItem(out.get(), 0, incref(object(v.d_x).ptr()));
            PyList_SetItem(out.get(), 1, incref(object(v.d_y).ptr()));
            return incref(out.get());
        }
    };

    struct CEGUI_UVector2_From {
        static void* convertible(PyObject* obj) {
            if (PySequence_Check(obj) && PySequence_Size(obj) == 2) {
                handle<> o1(PySequence_GetItem(obj, 0));
                handle<> o2(PySequence_GetItem(obj, 1));
                if (CEGUI_UDim_From::convertible(o1.get()) &&
                    CEGUI_UDim_From::convertible(o2.get())) {
                    return obj;
                }
            }
            return nullptr;
        }
        static void construct(PyObject* obj, converter::rvalue_from_python_stage1_data* data) {
            using namespace ui;
            handle<> u1(PySequence_GetItem(obj, 0));
            handle<> u2(PySequence_GetItem(obj, 1));

            handle<> nx1(PySequence_GetItem(u1.get(), 0));
            handle<> nx2(PySequence_GetItem(u1.get(), 1));
            handle<> ny1(PySequence_GetItem(u2.get(), 0));
            handle<> ny2(PySequence_GetItem(u2.get(), 1));

            handle<> fx1(PyNumber_Float(nx1.get()));
            handle<> fx2(PyNumber_Float(nx2.get()));
            handle<> fy1(PyNumber_Float(ny1.get()));
            handle<> fy2(PyNumber_Float(ny2.get()));

            KH_GET_STORAGE(UVector2);
            new(storage) UVector2(UDim((float)PyFloat_AS_DOUBLE(fx1.get()),
                                       (float)PyFloat_AS_DOUBLE(fx2.get())),
                                  UDim((float)PyFloat_AS_DOUBLE(fy1.get()),
                                       (float)PyFloat_AS_DOUBLE(fy2.get())));
            KH_SET_STORAGE;
        }
    };

    struct CEGUI_String_To {
        static PyObject* convert(ui::String const& s) {
            return PyString_FromStringAndSize(s.c_str(), s.size());
        }
    };

    struct CEGUI_String_From {
        static void* convertible(PyObject* obj) {
            if (PyString_CheckExact(obj))
                return obj;
            return nullptr;
        }
        static void construct(PyObject* obj, converter::rvalue_from_python_stage1_data* data) {
            KH_GET_STORAGE(ui::String);
            new(storage) ui::String((char const*)PyString_AS_STRING(obj), PyString_GET_SIZE(obj));
            KH_SET_STORAGE;
        }
    };

    // FVector2I from sequence

    struct Vector2I_From {
        static void* convertible(PyObject* obj) {
            if (PySequence_Check(obj) && PySequence_Size(obj) == 2) {
                return obj;
            }
            return nullptr;
        }
        static void construct(PyObject* obj, converter::rvalue_from_python_stage1_data* data) {
            handle<> o1(PySequence_GetItem(obj, 0));
            handle<> o2(PySequence_GetItem(obj, 1));

            int n1 = int(PyInt_AS_LONG(PyInt_CheckExact(o1.get()) ? o1.get() : handle<>(PyNumber_Int(o1.get())).get()));
            int n2 = int(PyInt_AS_LONG(PyInt_CheckExact(o2.get()) ? o2.get() : handle<>(PyNumber_Int(o2.get())).get()));

            KH_GET_STORAGE(FVector2I);
            new(storage) FVector2I(n1, n2);
            KH_SET_STORAGE;
        }
    };
    
    // FVector2F from sequence

    struct Vector2F_From {
        static void* convertible(PyObject* obj) {
            if (PySequence_Check(obj) && PySequence_Size(obj) == 2) {
                return obj;
            }
            return nullptr;
        }
        static void construct(PyObject* obj, converter::rvalue_from_python_stage1_data* data) {
            handle<> o1(PySequence_GetItem(obj, 0));
            handle<> o2(PySequence_GetItem(obj, 1));

            float n1 = float(PyFloat_AS_DOUBLE(PyFloat_CheckExact(o1.get()) ? o1.get() : handle<>(PyNumber_Float(o1.get())).get()));
            float n2 = float(PyFloat_AS_DOUBLE(PyFloat_CheckExact(o2.get()) ? o2.get() : handle<>(PyNumber_Float(o2.get())).get()));

            KH_GET_STORAGE(FVector2F);
            new(storage) FVector2F(n1, n2);
            KH_SET_STORAGE;
        }
    };

    template <class T>
    struct TVector3_From {
        static_assert(boost::is_pod<T>::value, "vectors must be composed of PODs");
        static void* convertible(PyObject* obj) {
            if (PySequence_Check(obj) && PySequence_Size(obj) == 3) {
                return obj;
            }
            return nullptr;
        }
        static void construct(PyObject* obj, converter::rvalue_from_python_stage1_data* data) {
            handle<> o1(PySequence_GetItem(obj, 0));
            handle<> o2(PySequence_GetItem(obj, 1));
            handle<> o3(PySequence_GetItem(obj, 2));

            T n1 = extract<T>(o1.get());
            T n2 = extract<T>(o2.get());
            T n3 = extract<T>(o3.get());

            KH_GET_STORAGE(TVector3<T>);
            new(storage) TVector3<T>(n1, n2, n3);
            KH_SET_STORAGE;
        }
    };

    template <class T>
    struct TSize_From {
        static_assert(boost::is_pod<T>::value, "sizes must be composed of PODs");
        static void* convertible(PyObject* obj) {
            if (PySequence_Check(obj) && PySequence_Size(obj) == 2) {
                return obj;
            }
            return nullptr;
        }
        static void construct(PyObject* obj, converter::rvalue_from_python_stage1_data* data) {
            handle<> o1(PySequence_GetItem(obj, 0));
            handle<> o2(PySequence_GetItem(obj, 1));

            T n1 = extract<T>(o1.get());
            T n2 = extract<T>(o2.get());

            KH_GET_STORAGE(TSize<T>);
            new(storage) TSize<T>(n1, n2);
            KH_SET_STORAGE;
        }
    };

    template <class T>
    struct TRect_From {
        static_assert(boost::is_pod<T>::value, "rects must be composed of PODs");
        static void* convertible(PyObject* obj) {
            if (PySequence_Check(obj) && PySequence_Size(obj) == 4) {
                return obj;
            }
            return nullptr;
        }
        static void construct(PyObject* obj, converter::rvalue_from_python_stage1_data* data) {
            handle<> o1(PySequence_GetItem(obj, 0));
            handle<> o2(PySequence_GetItem(obj, 1));
            handle<> o3(PySequence_GetItem(obj, 2));
            handle<> o4(PySequence_GetItem(obj, 3));

            T n1 = extract<T>(o1.get());
            T n2 = extract<T>(o2.get());
            T n3 = extract<T>(o3.get());
            T n4 = extract<T>(o4.get());

            KH_GET_STORAGE(TRect<T>);
            new(storage) TRect<T>(n1, n2, n3, n4);
            KH_SET_STORAGE;
        }
    };

    // Helper functions for registering converters

    template <class Type, class ToStruct>
    static void RegTo() {
        to_python_converter<Type, ToStruct>();
    }
    template <class Type, class FromStruct>
    static void RegFrom() {
        converter::registry::push_back(
            &FromStruct::convertible,
            &FromStruct::construct,
            type_id<Type>()
        );
    }
    template <class Type, class ToStruct, class FromStruct>
    static void RegCon() {
        RegTo<Type, ToStruct>();
        RegFrom<Type, FromStruct>();
    }
    // Called from the BOOST_PYTHON_MODULE
    void _InitConverters() {
        //to_python_converter<CEGUI_UDim, CEGUI_UDim_To>();
        //converter::registry::push_back(&CEGUI_UDim_From::convertible,
        //                                   &CEGUI_UDim_From::construct,
        //                                    type_id<CEGUI_UDim>());

        RegCon<CEGUI_UDim, CEGUI_UDim_To, CEGUI_UDim_From>();
        RegCon<ui::String, CEGUI_String_To, CEGUI_String_From>();
        RegCon<ui::UVector2, CEGUI_UVector2_To, CEGUI_UVector2_From>();
        RegFrom<FVector2I, Vector2I_From>();
        RegFrom<FVector2F, Vector2F_From>();
        //RegFrom<FVector3I, Vector3I_From>();
        //RegFrom<FVector3F, Vector3F_From>();
        RegFrom<FVector3I, TVector3_From<FVector3I::DataType>>();
        RegFrom<FVector3F, TVector3_From<FVector3F::DataType>>();
        RegFrom<FSizeI, TSize_From<FSizeI::DataType>>();
        RegFrom<FSizeF, TSize_From<FSizeF::DataType>>();
        RegFrom<FRectI, TRect_From<FRectI::DataType>>();
        RegFrom<FRectF, TRect_From<FRectF::DataType>>();
    }

}}